// Copyright Gary Evans 2006-2007.

using System;
using System.Collections.Generic;
using System.Text;

using NUnit.Framework;

namespace Taumuon.Jabuka.Physics.Test
{
    /// <summary>
    /// Tests the operation of the Vector class.
    /// </summary>
    [TestFixture]
    public class TestVector
    {
		/// <summary>
		/// Tests the Vector's default constructor.
		/// </summary>
		[Test]
		public void TestDefaultConstructor()
		{
			Vector vector = new Vector();
			Assert.IsTrue(
				AreVectorValuesCorrect(vector, 0.0, 0.0, 0.0));
		}

		/// <summary>
		/// Tests the SetValues method on the Vector.
		/// </summary>
		[Test]
		public void TestSetValues()
		{
			Vector vector = new Vector();
			vector.SetValues(2.0, -3.0, 4.0);
			Assert.IsTrue(
				AreVectorValuesCorrect(vector, 2.0, -3.0, 4.0));
		}

        /// <summary>
        /// Tests the addition of two vectors.
        /// </summary>
        [Test]
        public void TestVectorAddition()
        {
            Vector vector1 = new Vector(1.0, 2.0, 3.0);
            Vector vector2 = new Vector(-2.0, 1.0, 1.0);
            Vector vector3 = vector1 + vector2;
            // Check that the original vectors are unchanged
            //  following addition
            Assert.IsTrue(
                AreVectorValuesCorrect(vector1, 1.0, 2.0, 3.0));
            Assert.IsTrue(
                AreVectorValuesCorrect(vector2, -2.0, 1.0, 1.0));
            // Check the result of addition
            Assert.IsTrue(
                AreVectorValuesCorrect(vector3, -1.0, 3.0, 4.0));
        }

        /// <summary>
        /// Tests the addition of two vectors.
        /// </summary>
        [Test]
        public void TestVectorSubtraction()
        {
            Vector vector1 = new Vector(1.0, 2.0, 3.0);
            Vector vector2 = new Vector(-2.0, 1.0, 1.0);
            Vector vector3 = vector1 - vector2;
            // Check that the original vectors are unchanged
            //  following addition
            Assert.IsTrue(
                AreVectorValuesCorrect(vector1, 1.0, 2.0, 3.0));
            Assert.IsTrue(
                AreVectorValuesCorrect(vector2, -2.0, 1.0, 1.0));
            // Check the result of addition
            Assert.IsTrue(
                AreVectorValuesCorrect(vector3, 3.0, 1.0, 2.0));
        }

        /// <summary>
        /// Tests the multiplication of a Vector by a scalar,
        /// with the scalar operand on the right hand side.
        /// </summary>
        [Test]
        public void TestVectorMultiplyByRightHandScalar()
        {
            Vector vector1 = new Vector(1.0, 2.0, 3.0);
            Vector vector2 = vector1 * 2.0;
            // Check that the original vector is unchanged
            //  following multiplication by a scalar
            Assert.IsTrue(
                AreVectorValuesCorrect(vector1, 1.0, 2.0, 3.0));
            // Check the result of multiplication by a scalar
            Assert.IsTrue(
                AreVectorValuesCorrect(vector2, 2.0, 4.0, 6.0));
        }

        /// <summary>
        /// Tests the multiplication of a Vector by a scalar,
        /// with the scalar operand on the left hand side.
        /// </summary>
        [Test]
        public void TestVectorMultiplyByLeftHandScalar()
        {
            Vector vector1 = new Vector(1.0, 2.0, 3.0);
            Vector vector2 = 2.0 * vector1;
            // Check that the original vector is unchanged
            //  following multiplication by a scalar
            Assert.IsTrue(
                AreVectorValuesCorrect(vector1, 1.0, 2.0, 3.0));
            // Check the result of multiplication by a scalar
            Assert.IsTrue(
                AreVectorValuesCorrect(vector2, 2.0, 4.0, 6.0));
        }

        /// <summary>
        /// Tests the Vector's magnitude property
        /// </summary>
        [Test]
        public void TestVectorMagnitude()
        {
            Vector vector1 = new Vector(3.0, 4.0, 0.0);
            Assert.IsTrue(MathUtils.IsEqualWithinTol(
                vector1.Magnitude, 5.0, Tolerance));
            // Check original unchanged
            Assert.IsTrue(
                AreVectorValuesCorrect(vector1, 3.0, 4.0, 0.0));

            Vector vector2 = new Vector(5.0, -5.0, 7.0);
            Assert.IsTrue(MathUtils.IsEqualWithinTol(
                vector2.Magnitude, 9.9498743710662, Tolerance));
        }

        /// <summary>
        /// Tests that the dot product of two perpendicular vectors is zero
        /// </summary>
        [Test]
        public void TestDotProductPerpendicular()
        {
            Vector vector1 = new Vector(1.0, 1.0, 0.0);
            Vector vector2 = new Vector(1.0, -1.0, 0.0);

            double dotProduct = vector1.DotProduct(vector2);
            Assert.IsTrue(MathUtils.IsEqualWithinTol(dotProduct, 0.0, Tolerance), "Dot product should be 0.0, actual = " + dotProduct.ToString());

            double dotProduct2 = vector2.DotProduct(vector1);
            Assert.IsTrue(MathUtils.IsEqualWithinTol(dotProduct2, 0.0, Tolerance), "Dot product 2 should be 0.0, actual = " + dotProduct2.ToString());

            Vector vector3 = new Vector(0.0, 0.0, -1.0);
            double dotProduct3 = vector1.DotProduct(vector3);
            Assert.IsTrue(MathUtils.IsEqualWithinTol(dotProduct3, 0.0, Tolerance), "Dot product 3 should be 0.0, actual = " + dotProduct3.ToString());

            // check original unchanged
            Assert.IsTrue(
                AreVectorValuesCorrect(vector1, 1.0, 1.0, 0.0));
            Assert.IsTrue(
                AreVectorValuesCorrect(vector2, 1.0, -1.0, 0.0));
            Assert.IsTrue(
                AreVectorValuesCorrect(vector3, 0.0, 0.0, -1.0));
        }

        /// <summary>
        /// Tests the dot product of two parallel vectors.
        /// </summary>
        [Test]
        public void TestDotProductParallel()
        {
            Vector vector1 = new Vector(0.5, -0.7, 0.9);
            Vector vector2 = new Vector(0.5, -0.7, 0.9);

            double dotProduct = vector1.DotProduct(vector2);
            double expectedResult = vector2.Magnitude * vector1.Magnitude;
            Assert.IsTrue(MathUtils.IsEqualWithinTol(dotProduct, expectedResult, Tolerance), "Dot Product should be " + expectedResult.ToString() + ", actual = " + dotProduct.ToString());

            double dotProduct2 = vector1.DotProduct(vector2);
            double expectedResult2 = vector1.Magnitude * vector2.Magnitude;
            Assert.IsTrue(MathUtils.IsEqualWithinTol(dotProduct2, expectedResult2, Tolerance), "Dot product 2 should be " + expectedResult2.ToString() + ", actual = " + dotProduct2.ToString());

            Vector vector3 = vector2 * 0.7;
            double dotProduct3 = vector3.DotProduct(vector1);
            double expectedResult3 = expectedResult2 * 0.7;
            Assert.IsTrue(MathUtils.IsEqualWithinTol(dotProduct3, expectedResult3, Tolerance), "Dot product 3 should be " + expectedResult3.ToString() + ",actual = " + dotProduct3.ToString());

            vector3 = vector3 * -1.0;
            double dotProduct4 = vector2.DotProduct(vector3);
            Assert.IsTrue(MathUtils.IsEqualWithinTol(dotProduct4, -expectedResult3, Tolerance), "Dot product 4 should be " + (-expectedResult3).ToString() + ", actual = " + dotProduct4.ToString());
        }

		/// <summary>
		/// Test the cross product of two vectors.
		/// </summary>
		[Test]
		public void TestCrossProductParallelVectors()
		{
			Vector vector1 = new Vector(1.0, 1.0, 0.0);
			Vector vector2 = new Vector(2.0, 2.0, 0.0);

			Vector vector3 = vector1.CrossProduct(vector2);

			Assert.IsTrue(
				AreVectorValuesCorrect(vector3, 0.0, 0.0, 0.0));

			// check original unchanged
			Assert.IsTrue(
				AreVectorValuesCorrect(vector1, 1.0, 1.0, 0.0));
			Assert.IsTrue(
				AreVectorValuesCorrect(vector2, 2.0, 2.0, 0.0));
		}

        /// <summary>
        /// Another cross product test
        /// </summary>
        [Test]
        public void TestCrossProduct2()
        {
            Vector vector1 = new Vector(1.0, 0.0, 0.0);
            Vector vector2 = new Vector(0.0, 1.0, 0.0);

            Vector vector3 = vector1.CrossProduct(vector2);

            Assert.IsTrue(
                AreVectorValuesCorrect(vector3, 0.0, 0.0, 1.0));

            // check original unchanged
            Assert.IsTrue(
                AreVectorValuesCorrect(vector1, 1.0, 0.0, 0.0));
            Assert.IsTrue(
                AreVectorValuesCorrect(vector2, 0.0, 1.0, 0.0));
        }

		/// <summary>
		/// Test the cross product of two vectors.
		/// </summary>
		[Test]
		public void TestCrossProduct()
		{
			Vector vector1 = new Vector(3.0, -3.0, 1.0);
			Vector vector2 = new Vector(4.0, 9.0, 2.0);

			Vector vector3 = vector1.CrossProduct(vector2);

			Assert.IsTrue(
				AreVectorValuesCorrect(vector3, -15.0, -2.0, 39.0));

			// check original unchanged
			Assert.IsTrue(
				AreVectorValuesCorrect(vector1, 3.0, -3.0, 1.0));
			Assert.IsTrue(
				AreVectorValuesCorrect(vector2, 4.0, 9.0, 2.0));
		}

		/// <summary>
		/// Tests the vector's Normalise() method.
		/// </summary>
		[Test]
		public void TestVectorNormalise()
		{
			// vector with magnitude 0 - check normalise does nothing
			Vector vector1 = new Vector(0.0, 0.0, 0.0);
			Assert.IsTrue(
				AreVectorValuesCorrect(vector1.Normalise(), 0.0, 0.0, 0.0));

			// vector magnitude < 1
			Vector vector2 = new Vector(0.1, 0.2, -0.1);
			double magnitude = vector2.Magnitude;
            Vector vector3 = vector2.Normalise();
			vector3.Normalise();
			Assert.IsTrue(MathUtils.IsEqualWithinTol(vector3.Normalise().Magnitude, 1.0, Tolerance),
				"vector 3 magnitude should be 1.0, actual = " + vector3.Magnitude.ToString());
			Vector vector4 = vector3 * magnitude;
			Assert.IsTrue(
				AreVectorValuesCorrect(vector4, 0.1, 0.2, -0.1));

			Vector vector5 = new Vector(-12.2, 7.0, -5.0);
			double magnitude2 = vector5.Magnitude;
            Vector vector6 = vector5.Normalise();
			Assert.IsTrue(MathUtils.IsEqualWithinTol(vector6.Magnitude, 1.0, Tolerance),
				"vector 6 magnitude should be 1.0, actual = " + vector6.Magnitude.ToString());
			Vector vector7 = vector6 * magnitude2;
			Assert.IsTrue(
				AreVectorValuesCorrect(vector7, -12.2, 7.0, -5.0));

			// check originals unchanged
			Assert.IsTrue(
				AreVectorValuesCorrect(vector2, 0.1, 0.2, -0.1));
			Assert.IsTrue(
				AreVectorValuesCorrect(vector5, -12.2, 7.0, -5.0));
		}

        /// <summary>
        /// Tests that the Vector is cloned correctly.
        /// </summary>
        [Test]
        public void TestClone()
        {
            Vector vector = new Vector(1.0, 2.0, 3.0);
            Vector vector2 = vector.Clone() as Vector;
            Assert.IsNotNull(vector2);
            Assert.IsFalse(object.ReferenceEquals(vector, vector2));
            Assert.IsTrue(
                AreVectorValuesCorrect(vector2, 1.0, 2.0, 3.0));
        }

        internal static bool AreVectorValuesCorrect(Vector vector,
            double x, double y, double z)
        {
            return vector != null
				&& MathUtils.IsEqualWithinTol(vector.X, x, Tolerance)
                && MathUtils.IsEqualWithinTol(vector.Y, y, Tolerance)
                && MathUtils.IsEqualWithinTol(vector.Z, z, Tolerance);
        }

        private static double Tolerance = 1e-10;
    }
}
